
//
// This file was written by Saurav Mohapatra and modified by Nikolaus Gebhardt.
// See CCSMLoader.h for details.

#include "CCSMLoader.h"
#include "os.h"
#include "IFileSystem.h"
#include "IReadFile.h"
#include "ISceneManager.h"
#include "IStringParameters.h"
#include "SMesh.h"
#include "IVideoDriver.h"
#include <string.h>
#include "SAnimatedMesh.h"

namespace irr
{
    namespace scene
    {
        //
        //	the CSM data types
        //
        struct color_rgb_t
        {
            s32 red;
            s32 green;
            s32 blue;

            void clear()
            {
                red = 0;
                green = 0;
                blue = 0;
            }
        };

        class BinaryFileReader;

        //
        //	The file header
        //
        class Header
        {
        public:

            static const s32 VERSION_4;
            static const s32 VERSION_4_1;

            Header(){ clear(); }
            virtual ~Header(){ clear(); }

            const s32 getVersion() const{ return version; }
            void clear(){ version = 0; }
            void load(BinaryFileReader* pReader);

        private:

            s32 version;
        };


        //
        //	The groups
        //
        class Group
        {
        public:

            Group(){ clear(); }
            virtual ~Group(){ clear(); }

            void clear();
            void load(BinaryFileReader* pReader);

            s32 getFlags() const { return flags; }
            s32 getParentGroupID() const { return parentGroup; }
            const core::stringc& getProperties() const { return props; }
            const color_rgb_t* getColor() const { return &color; }

        private:

            s32 flags;
            s32 parentGroup;
            core::stringc props;
            color_rgb_t color;
        };


        //
        //	The visgroups
        //
        class VisGroup
        {
        public:

            VisGroup(){ clear(); }
            virtual ~VisGroup(){ clear(); }
            void clear();
            void load(BinaryFileReader* pReader);

            s32 getFlags() const{ return flags; }
            const core::stringc& getName() const{ return name; }
            const color_rgb_t* getColor() const{ return &color; }

        private:

            core::stringc name;
            s32 flags;
            color_rgb_t color;
        };


        //
        //	Lightmaps
        //
        class LightMap
        {
        public:

            LightMap() : pixelData(0){ clear(); }
            virtual ~LightMap(){ clear(); }
            void clear();
            void load(BinaryFileReader* pReader);
            const s32 getWidth() const{ return width; }
            const s32 getHeight() const{ return height; }
            const s32* getPixelData() const{ return pixelData; }

        private:

            s32 width;
            s32 height;
            s32* pixelData;
        };

        struct Triangle
        {
            s32 a,b,c;
        };


        struct Line
        {
            s32 a,b;
        };


        class Vertex
        {
        public:

            Vertex(){ clear(); }
            virtual ~Vertex(){ clear(); }
            void clear();
            void load(BinaryFileReader* pReader);

            const core::vector3df* getPosition() const { return &position; }
            const core::vector3df* getNormal() const { return &normal; }
            const color_rgb_t* getColor() const { return &color; }
            const core::vector3df* getTextureCoordinates() const { return &texCoords; }
            const core::vector3df* getLightMapCoordinates() const { return &lmapCoords; }

        private:

            core::vector3df position;
            core::vector3df normal;
            color_rgb_t color;
            core::vector3df texCoords;
            core::vector3df lmapCoords;
        };


        class Surface
        {
        public:

            Surface() { clear(); }
            virtual ~Surface(){ clear(); }

            void clear();
            void load(BinaryFileReader *pReader);

            const s32 getFlags() const{ return flags; }
            const core::stringc& getTextureName() const{ return textureName; }
            const s32 getLightMapId() const{ return lightMapId; }
            const core::vector2df* getUVOffset() const{ return &uvOffset; }
            const core::vector2df* getUVScale() const{ return &uvScale; }
            const f32 getUVRotation() const{ return uvRotation; }

            const s32 getVertexCount() const{ return vertices.size(); }
            const Vertex* getVertexAt(const s32 index) const{ return vertices[index]; }

            const s32 getTriangleCount() const{ return triangles.size(); }
            const Triangle& getTriangleAt(const s32 index) const{ return triangles[index]; }

        private:

            s32 flags;
            core::stringc textureName;
            s32 lightMapId;
            core::vector2df uvOffset;
            core::vector2df uvScale;
            f32 uvRotation;
            core::array<Vertex*> vertices;
            core::array<Triangle> triangles;
            core::array<Line> lines;
        };

        class Mesh
        {
        public:

            Mesh(){ clear(); }
            virtual ~Mesh(){ clear(); }

            void clear();
            void load(BinaryFileReader* pReader, bool bReadVisGroups);

            const s32 getFlags() const { return flags; }
            const s32 getGroupID() const { return groupId; }
            const core::stringc& getProperties() const { return props; }
            const color_rgb_t* getColor() const { return &color; }
            const core::vector3df* getPosition() const { return &position; }
            const s32 getVisgroupID() const { return visgroupId; }
            const s32 getSurfaceCount() const { return surfaces.size(); }
            const Surface* getSurfaceAt(const s32 index) const { return surfaces[index]; }

        private:

            s32 flags;
            s32 groupId;
            core::stringc props;
            color_rgb_t color;
            core::vector3df position;
            s32 visgroupId;

            core::array<Surface*> surfaces;
        };

        class Entity
        {
        public:

            Entity() { clear(); }
            virtual ~Entity() { clear(); }

            void clear();
            void load(BinaryFileReader* pReader);
            const s32 getVisgroupID() const { return visgroupId; }
            const s32 getGroupID() const { return groupId; }
            const core::stringc& getProperties() const { return props; }
            const core::vector3df* getPosition() const { return &position; }

        private:

            s32 visgroupId;
            s32 groupId;
            core::stringc props;
            core::vector3df position;
        };


        class CameraData
        {
        public:

            CameraData(){ clear(); }
            virtual ~CameraData(){ clear(); }

            void clear();
            void load(BinaryFileReader* pReader);

            const core::vector3df* getPosition(){ return &position; }
            const f32 getPitch(){ return pitch; }
            const f32 getYaw(){ return yaw; }

        private:

            core::vector3df position;
            f32 pitch;
            f32 yaw;
        };

        //
        //	A CSM File
        //
        class CSMFile
        {
        public:

            CSMFile(){ clear(); }
            virtual ~CSMFile(){ clear(); }
            void clear();
            void load(BinaryFileReader* pReader);

            const Header* getHeader() const{ return &header; }

            const s32 getGroupCount() const{ return groups.size(); }
            const Group* getGroupAt(const s32 index) const{ return groups[index]; }

            const s32 getVisGroupCount() const{ return visgroups.size(); }
            const VisGroup* getVisGroupAt(const s32 index) const{ return visgroups[index]; }

            const s32 getLightMapCount() const{ return lightmaps.size(); }
            const LightMap* getLightMapAt(const s32 index) const { return lightmaps[index]; }

            const s32 getMeshCount() const{ return meshes.size(); }
            const Mesh* getMeshAt(const s32 index) const{ return meshes[index]; }

            const s32 getEntityCount() const{ return entities.size(); }
            const Entity* getEntityAt(const s32 index) const{ return entities[index]; }

            const CameraData* getCameraData() const{ return &cameraData; }

        private:

            Header header;
            core::array<Group*> groups;
            core::array<VisGroup*> visgroups;
            core::array<LightMap*> lightmaps;
            core::array<Mesh*> meshes;
            core::array<Entity*> entities;
            CameraData cameraData;
        };


        //
        //	A Binary File Reader
        //
        class BinaryFileReader
        {
        public:

            BinaryFileReader(io::IReadFile* pFile, bool closeWhenDone=true);
            virtual ~BinaryFileReader();

            virtual s32 readBuffer(void* buffer, s32 len);

            s32 readLong();
            f32 readFloat();
            u8  readByte();

            core::stringc readString();
            void readVec3f(core::vector3df* v);
            void readVec2f(core::vector2df* v);
            void readColorRGB(color_rgb_t* color);

        private:

            io::IReadFile *file;
            bool autoClose;

        };

        CCSMLoader::CCSMLoader(scene::ISceneManager* manager, io::IFileSystem* fs) 
            : FileSystem(fs), SceneManager(manager)
        {
        }

        CCSMLoader::~CCSMLoader()
        {
        }

        //! returns true if the file maybe is able to be loaded by this class
        //! based on the file extension (e.g. ".bsp")
        bool CCSMLoader::isALoadableFileExtension(const c8* fileName)
        {
            return strstr(fileName, ".csm")!=0;
        }

        //! creates/loads an animated mesh from the file.
        IAnimatedMesh* CCSMLoader::createMesh(irr::io::IReadFile* file)
        {
            file->grab(); // originally, this loader created the file on its own.

            scene::IMesh* m = createCSMMesh(file);

            if (!m)
                return 0;

            SAnimatedMesh* am = new SAnimatedMesh();
            am->Type = EAMT_CSM;
            am->addMesh(m);
            m->drop();

            am->recalculateBoundingBox();
            return am;
        }

        scene::IMesh* CCSMLoader::createCSMMesh(irr::io::IReadFile* file)
        {
            if (!file)
                return 0;

            if(file)
            {
                BinaryFileReader reader(file, true);
                CSMFile csmFile;
                csmFile.load(&reader);

                scene::IMesh* pMesh = createIrrlichtMesh(&csmFile, 
                    SceneManager->getParameters()->getParameter(CSM_TEXTURE_PATH),
                    file->getFileName());
                return pMesh;
            }

            return 0;
        }

        scene::IMesh* CCSMLoader::createIrrlichtMesh(const CSMFile* csmFile, 
            core::stringc textureRoot, const c8* lmprefix)
        {
            scene::SMesh *pMesh = new scene::SMesh();
            video::IVideoDriver* driver = SceneManager->getVideoDriver();

            for(s32 l = 0; l<csmFile->getLightMapCount(); l++)
            {
                const LightMap* lmap = csmFile->getLightMapAt(l);

                core::stringc lmapName = lmprefix;
                lmapName += "LMAP_";
                lmapName += (int)(l+1);
                os::Printer::log("CCSMLoader loading light map", lmapName.c_str());

                video::IImage* lmapImg = driver->createImageFromData(
                    video::ECF_A8R8G8B8, 
                    core::dimension2d<s32>(lmap->getWidth(),lmap->getHeight()),
                    (void *)(lmap->getPixelData()));

                driver->addTexture(lmapName.c_str(), lmapImg);
                lmapImg->drop();

            }

            for(s32 m = 0; m<csmFile->getMeshCount(); m++)
            {
                const Mesh* mshPtr = csmFile->getMeshAt(m);

                for(s32 s = 0; s < mshPtr->getSurfaceCount(); s++)
                {
                    const Surface* surface = mshPtr->getSurfaceAt(s);

                    core::stringc texName = textureRoot;
                    texName+= "/";
                    texName+= surface->getTextureName();

                    video::ITexture* texture = driver->getTexture(texName.c_str());
                    scene::SMeshBufferLightMap *buffer = new scene::SMeshBufferLightMap();

                    //material
                    core::stringc lmapName = lmprefix;
                    lmapName += "LMAP_";
                    lmapName += (int)surface->getLightMapId();

                    buffer->Material.Texture1 = texture;
                    buffer->Material.Texture2 = driver->getTexture(lmapName.c_str());
                    buffer->Material.Lighting = false;
                    buffer->Material.MaterialType = video::EMT_LIGHTMAP_M4;

                    for(s32 v = 0; v < surface->getVertexCount(); v++)
                    {
                        const Vertex *vtxPtr = surface->getVertexAt(v);
                        video::S3DVertex2TCoords vtx;
                        vtx.Pos = *(vtxPtr->getPosition());
                        vtx.Normal = *(vtxPtr->getPosition());
                        vtx.Color.set(255,vtxPtr->getColor()->red,vtxPtr->getColor()->green,vtxPtr->getColor()->blue);
                        vtx.TCoords.set(vtxPtr->getTextureCoordinates()->X,vtxPtr->getTextureCoordinates()->Y);
#ifndef _IRR_PSP_
                        vtx.TCoords2.set(vtxPtr->getLightMapCoordinates()->X,0 - vtxPtr->getLightMapCoordinates()->Y);
#endif
                        buffer->Vertices.push_back(vtx);
                    }

                    for(s32 t = 0; t < surface->getTriangleCount(); t++)
                    {
                        const Triangle& tri = surface->getTriangleAt(t);
                        buffer->Indices.push_back(tri.a);
                        buffer->Indices.push_back(tri.c);
                        buffer->Indices.push_back(tri.b);
                    }

                    buffer->recalculateBoundingBox();
                    pMesh->addMeshBuffer(buffer);
                    buffer->drop();
                }
            }

            pMesh->recalculateBoundingBox();
            return pMesh;
        }

        const s32 Header::VERSION_4 = 4;
        const s32 Header::VERSION_4_1 = 5;

        void Header::load(BinaryFileReader* pReader)
        {
            version = pReader->readLong();
        }

        void Group::clear()
        {
            color.clear();
            flags = 0;
            parentGroup = 0;
            props = "";
        }

        void Group::load(BinaryFileReader* pReader)
        {
            flags = pReader->readLong();
            parentGroup = pReader->readLong();
            props = pReader->readString();
            pReader->readColorRGB(&color);
        }

        void VisGroup::clear()
        {
            color.clear();
            flags = 0;
            name = "";

        }

        void VisGroup::load(BinaryFileReader* pReader)
        {
            name = pReader->readString();
            flags = pReader->readLong();
            pReader->readColorRGB(&color);
        }

        void LightMap::clear()
        {
            if(pixelData)
            {
                delete[] pixelData;
                pixelData = 0;
            }
            width = height = 0;
        }

        void LightMap::load(BinaryFileReader* pReader)
        {
            width = pReader->readLong();
            height = pReader->readLong();
            pixelData = new s32[width * height];
            pReader->readBuffer(pixelData, width * height * sizeof(s32));
        }

        void Mesh::clear()
        {
            flags = 0;
            groupId = 0;
            visgroupId = 0;
            props = "";
            color.clear();
            position.set(0,0,0);

            for(u32 s = 0; s < surfaces.size(); s++)
            {
                if(surfaces[s])
                {
                    delete surfaces[s];
                }
            }
            surfaces.clear();
        }

        void Mesh::load(BinaryFileReader* pReader, bool bReadVisGroups)
        {
            flags = pReader->readLong();
            groupId = pReader->readLong();
            props = pReader->readString();
            pReader->readColorRGB(&color);
            pReader->readVec3f(&position);
            if(bReadVisGroups)
                visgroupId = pReader->readLong();
            else
                visgroupId = 0;

            s32 count = pReader->readLong();

            for(s32 i = 0; i < count; i++)
            {
                Surface* surf = new Surface();
                surf->load(pReader);
                surfaces.push_back(surf);
            }
        }

        void Surface::clear()
        {
            flags = 0;
            lightMapId = 0;
            textureName = 0;
            uvOffset.set(0,0);
            uvScale.set(0,0);
            uvRotation = 0;
            triangles.clear();
            lines.clear();

            for(u32 v = 0; v < vertices.size(); v++)
                delete vertices[v];

            vertices.clear();
        }

        void Surface::load(BinaryFileReader* pReader)
        {

            flags = pReader->readLong();
            textureName = pReader->readString();

            lightMapId = pReader->readLong();
            pReader->readVec2f(&uvOffset);
            pReader->readVec2f(&uvScale);
            uvRotation = pReader->readFloat();
            s32 vtxCount = pReader->readLong();
            s32 triCount = pReader->readLong();
            s32 lineCount = pReader->readLong();

            for(s32 v = 0; v < vtxCount; v++)
            {
                Vertex *vtx = new Vertex();
                vtx->load(pReader);
                vertices.push_back(vtx);
            }

            for(s32 t = 0; t < triCount; t++)
            {
                Triangle tri;
                pReader->readBuffer(&tri, sizeof(tri));
                triangles.push_back(tri);
            }

            for(s32 l = 0; l < lineCount; l++)
            {
                Line line;
                pReader->readBuffer(&line,sizeof(line));
                lines.push_back(line);

            }

        }

        void Vertex::clear()
        {
            position.set(0,0,0);
            normal.set(0,0,0);
            color.clear();
            texCoords.set(0,0,0);
            lmapCoords.set(0,0,0);

        }

        void Vertex::load(BinaryFileReader* pReader)
        {
            pReader->readVec3f(&position);
            pReader->readVec3f(&normal);
            pReader->readColorRGB(&color);
            pReader->readVec3f(&texCoords);
            pReader->readVec3f(&lmapCoords);
        }

        void Entity::clear()
        {
            visgroupId = groupId = 0;
            props = "";
            position.set(0,0,0);
        }

        void Entity::load(BinaryFileReader* pReader)
        {
            visgroupId = pReader->readLong();
            groupId = pReader->readLong();
            props = pReader->readString();
            pReader->readVec3f(&position);
        }

        void CameraData::clear()
        {
            position.set(0,0,0);
            pitch = 0;
            yaw = 0;
        }

        void CameraData::load(BinaryFileReader* pReader)
        {
            pReader->readVec3f(&position);
            pitch = pReader->readFloat();
            yaw = pReader->readFloat();
        }

        void CSMFile::clear()
        {
            header.clear();
            cameraData.clear();

            u32 x =0;
            for( x= 0; x < groups.size(); x++)
                delete groups[x];

            groups.clear();

            for(x= 0; x < visgroups.size(); x++)
                delete visgroups[x];

            visgroups.clear();

            for(x= 0; x < lightmaps.size(); x++)
                delete lightmaps[x];

            lightmaps.clear();

            for(x= 0; x < meshes.size(); x++)
                delete meshes[x];

            meshes.clear();

            for(x= 0; x < entities.size(); x++)
                delete entities[x];

            entities.clear();
        }

        void CSMFile::load(BinaryFileReader* pReader)
        {
            clear();

            header.load(pReader);

            //groups
            {

                s32 count = pReader->readLong();

                for (s32 i = 0; i < count; i++)
                {
                    Group* grp = new Group();
                    grp->load(pReader);
                    groups.push_back(grp);
                }
            }
            bool bHasVGroups = (header.getVersion() == Header::VERSION_4_1);

            if (bHasVGroups)
            {
                //visgroups
                s32 count = pReader->readLong();

                for (s32 i = 0; i < count; i++)
                {
                    VisGroup* grp = new VisGroup();
                    grp->load(pReader);
                    visgroups.push_back(grp);
                }
            }

            //lightmaps
            {

                s32 count = pReader->readLong();

                for(s32 i = 0; i < count; i++)
                {
                    LightMap* grp = new LightMap();
                    grp->load(pReader);
                    lightmaps.push_back(grp);
                }
            }

            //meshes
            {
                s32 count = pReader->readLong();

                for(s32 i = 0; i < count; i++)
                {
                    Mesh* grp = new Mesh();
                    grp->load(pReader,bHasVGroups);
                    meshes.push_back(grp);
                }
            }

            //entities
            {
                s32 count = pReader->readLong();

                for(s32 i = 0; i < count; i++)
                {
                    Entity* grp = new Entity();
                    grp->load(pReader);
                    entities.push_back(grp);
                }
            }

            //camera data
            cameraData.load(pReader);


        }

        BinaryFileReader::BinaryFileReader(io::IReadFile* pFile,
            bool closeWhenDone) 
            : file(pFile), autoClose(closeWhenDone)
        {
        }

        BinaryFileReader::~BinaryFileReader()
        {
            if(autoClose && file)
            {
                file->drop();
                file = 0;

            }
        }

        s32 BinaryFileReader::readBuffer(void* buffer, s32 len)
        {
            return file->read(buffer,len);

        }

        s32 BinaryFileReader::readLong()
        {
            s32 ret = 0;
            readBuffer(&ret,sizeof(ret));
            return ret;
        }

        f32 BinaryFileReader::readFloat()
        {
            f32 ret = 0;
            readBuffer(&ret,sizeof(ret));
            return ret;
        }

        u8  BinaryFileReader::readByte()
        {
            u8 ret = 0;
            readBuffer(&ret,sizeof(ret));
            return ret;
        }

        core::stringc BinaryFileReader::readString()
        {
            core::stringc str = "";
            c8 c = (c8)readByte();
            while(c != 0)
            {
                str += c;
                c = (c8)readByte();
            }
            return str;
        }

        void BinaryFileReader::readVec3f(core::vector3df* v)
        {
            v->X = readFloat();
            v->Y = readFloat();
            v->Z = readFloat();
        }

        void BinaryFileReader::readVec2f(core::vector2df* v)
        {
            v->X = readFloat();
            v->Y = readFloat();
        }

        void BinaryFileReader::readColorRGB(color_rgb_t* color)
        {
            readBuffer(color,sizeof(color_rgb_t));
        }

    } // end namespace 
} // end namespace 

